package com.xmmj.baselibrary.widget.wheelview

import kotlin.jvm.JvmOverloads
import android.text.TextPaint
import android.text.StaticLayout
import android.view.GestureDetector
import android.widget.Scroller
import android.view.MotionEvent
import android.view.GestureDetector.SimpleOnGestureListener
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.os.Handler
import android.os.Message
import android.text.Layout
import android.util.AttributeSet
import android.view.View
import android.view.animation.Interpolator
import com.xmmj.baselibrary.R
import java.lang.StringBuilder
import java.util.*

/**
 * 滚筒选择器
 */
class WheelView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : View(context, attrs, defStyle) {
    /**
     * WheelView 适配器
     */
    private var adapter: WheelAdapter? = null

    /**
     * 当前显示的条目索引
     */
    private var currentItem = 0

    /**
     * 条目宽度
     */
    private var itemsWidth = 0

    /**
     * 标签宽度
     */
    private var labelWidth = 0

    /**
     * 可见的条目数
     */
    private var visibleItems = DEF_VISIBLE_ITEMS

    /**
     * 获取每个条目的高度
     *
     * @return 条目的高度
     */
    /**
     * 条目高度
     */
    private var itemHeight = 0
        get() {
            //如果条目高度不为 0, 直接返回
            if (field != 0) {
                return field
                //如果条目的高度为 0, 并且普通条目布局不为null, 条目个数大于 2
            } else if (itemsLayout != null && itemsLayout!!.lineCount > 2) {
                /*
                  * itemsLayout.getLineTop(2) : 获取顶部第二行上面的垂直(y轴)位置, 如果行数等于
                  */
                field = itemsLayout!!.getLineTop(2) - itemsLayout!!.getLineTop(1)
                return field
            }

            //如果上面都不符合, 使用整体高度处以 显示条目数
            return height / visibleItems
        }

    /**
     * 绘制普通条目画笔
     */
    private var itemsPaint: TextPaint? = null

    /**
     * 绘制选中条目画笔
     */
    private var valuePaint: TextPaint? = null

    /**
     * 普通条目布局
     * StaticLayout 布局用于控制 TextView 组件, 一般情况下不会直接使用该组件,
     * 除非你自定义一个组件 或者 想要直接调用  Canvas.drawText() 方法
     */
    private var itemsLayout: StaticLayout? = null
    private var labelLayout: StaticLayout? = null

    /**
     * 选中条目布局
     */
    private var valueLayout: StaticLayout? = null
    /**
     * 获取标签
     *
     * @return the label
     */
    /**
     * 标签 在选中条目的右边出现
     */
    var label: String? = null
        private set
    /**
     * 选中条目的背景图片
     */
    //private Drawable centerDrawable;
    /**
     * 顶部阴影图片
     */
    //private GradientDrawable topShadow;
    /**
     * 底部阴影图片
     */
    //private GradientDrawable bottomShadow;
    /**
     * 是否在滚动
     */
    private var isScrollingPerformed = false

    /**
     * 滚动的位置
     */
    private var scrollingOffset = 0

    /**
     * 手势检测器
     */
    private var gestureDetector: GestureDetector? = null

    /**
     * Scroll 类封装了滚动动作.
     * 开发者可以使用 Scroll 或者 Scroll 实现类 去收集产生一个滚动动画所需要的数据, 返回一个急冲滑动的手势.
     * 该对象可以追踪随着时间推移滚动的偏移量, 但是这些对象不会自动向 View 对象提供这些位置.
     * 如果想要使滚动动画看起来比较平滑, 开发者需要在适当的时机  获取 和 使用新的坐标;
     */
    private var scroller: Scroller? = null

    /**
     * 之前所在的 y 轴位置
     */
    private var lastScrollY = 0

    /**
     * 是否循环
     */
    private var isCyclic = false

    /**
     * 条目改变监听器集合  封装了条目改变方法, 当条目改变时回调
     */
    private val changingListeners: MutableList<OnWheelChangedListener> = LinkedList()

    /**
     * 条目滚动监听器集合, 该监听器封装了 开始滚动方法, 结束滚动方法
     */
    private val scrollingListeners: MutableList<OnWheelScrollListener> = LinkedList()

    /**
     * 初始化数据
     *
     * @param context 上下文对象
     */
    private fun initData(context: Context, attrs: AttributeSet?) {
        val ta = getContext().obtainStyledAttributes(attrs, R.styleable.WheelView)
        TEXT_SIZE = ta.getDimension(R.styleable.WheelView_defaultTextSize, 30f).toInt()
        SELECT_TEXT_SIZE = ta.getDimension(R.styleable.WheelView_selectTextSize, 40f).toInt()
        ADDITIONAL_ITEM_HEIGHT =
            ta.getDimension(R.styleable.WheelView_additionalItemHeight, 35f).toInt()
        VALUE_TEXT_COLOR = ta.getColor(R.styleable.WheelView_selectTextColor, Color.BLACK)
        ITEMS_TEXT_COLOR = ta.getColor(R.styleable.WheelView_defaultTextColor, Color.GRAY)
        ITEM_OFFSET = -TEXT_SIZE
        ta.recycle()

        //创建一个手势处理
        gestureDetector = GestureDetector(context, gestureListener)
        /*
         * 是否允许长按操作,
         * 如果设置为 true 用户按下不松开, 会返回一个长按事件,
         * 如果设置为 false, 按下不松开滑动的话 会收到滚动事件.
         */gestureDetector!!.setIsLongpressEnabled(false)

        //使用默认的 时间 和 插入器 创建一个滚动器
        scroller = Scroller(context)
    }

    /**
     * 获取该 WheelView 的适配器
     *
     * @return 返回适配器
     */
    fun getAdapter(): WheelAdapter? {
        return adapter
    }

    /**
     * 设置适配器
     *
     * @param adapter 要设置的适配器
     */
    fun setAdapter(adapter: WheelAdapter?) {
        this.adapter = adapter
        mHandler.postDelayed({
            invalidateLayouts()
            invalidate()
        }, 500)
    }

    var mHandler = Handler()

    /**
     * 设置 Scroll 的插入器
     *
     * @param interpolator the interpolator
     */
    fun setInterpolator(interpolator: Interpolator?) {
        //强制停止滚动
        scroller!!.forceFinished(true)
        //创建一个 Scroll 对象
        scroller = Scroller(context, interpolator)
    }

    /**
     * 获取课件条目数
     *
     * @return the count of visible items
     */
    fun getVisibleItems(): Int {
        return visibleItems
    }

    /**
     * 设置可见条目数
     *
     * @param count the new count
     */
    fun setVisibleItems(count: Int) {
        visibleItems = count
        invalidate()
    }

    /**
     * 设置标签
     *
     * @param newLabel the label to set
     */
    fun setLabel(newLabel: String) {
        if (label == null || label != newLabel) {
            label = newLabel
            labelLayout = null
            invalidate()
        }
    }

    /**
     * 添加 WheelView 选择的元素改变监听器
     *
     * @param listener the listener
     */
    fun addChangingListener(listener: OnWheelChangedListener) {
        changingListeners.add(listener)
    }

    /**
     * 移除 WheelView 元素改变监听器
     *
     * @param listener the listener
     */
    fun removeChangingListener(listener: OnWheelChangedListener) {
        changingListeners.remove(listener)
    }

    /**
     * 回调元素改变监听器集合的元素改变监听器元素的元素改变方法
     *
     * @param oldValue 旧的 WheelView选中的值
     * @param newValue 新的 WheelView选中的值
     */
    protected fun notifyChangingListeners(oldValue: Int, newValue: Int) {
        for (listener in changingListeners) {
            listener.onChanged(this, oldValue, newValue)
        }
    }

    /**
     * 添加 WheelView 滚动监听器
     *
     * @param listener the listener
     */
    fun addScrollingListener(listener: OnWheelScrollListener) {
        scrollingListeners.add(listener)
    }

    /**
     * 移除 WheelView 滚动监听器
     *
     * @param listener the listener
     */
    fun removeScrollingListener(listener: OnWheelScrollListener) {
        scrollingListeners.remove(listener)
    }

    /**
     * 通知监听器开始滚动
     */
    protected fun notifyScrollingListenersAboutStart() {
        for (listener in scrollingListeners) {
            //回调开始滚动方法
            listener.onScrollingStarted(this)
        }
    }

    /**
     * 通知监听器结束滚动
     */
    protected fun notifyScrollingListenersAboutEnd() {
        for (listener in scrollingListeners) {
            //回调滚动结束方法
            listener.onScrollingFinished(this)
        }
    }

    /**
     * 获取当前选中元素的索引
     *
     * @return 当前元素索引
     */
    fun getCurrentItem(): Int {
        return currentItem
    }

    /**
     * 设置当前元素的位置, 如果索引是错误的 不进行任何操作
     * -- 需要考虑该 WheelView 是否能循环
     * -- 根据是否需要滚动动画来确定是 ①滚动到目的位置 还是 ②晴空所有条目然后重绘
     *
     * @param index    要设置的元素索引值
     * @param animated 动画标志位
     */
    fun setCurrentItem(index: Int, animated: Boolean) {
        //如果没有适配器或者元素个数为0 直接返回
        var index = index
        if (adapter == null || adapter!!.getItemsCount() == 0) {
            return  // throw?
        }
        //目标索引小于 0 或者大于 元素索引最大值(个数 -1)
        if (index < 0 || index >= adapter!!.getItemsCount()) {
            //入股WheelView 可循环, 修正索引值, 如果不可循环直接返回
            if (isCyclic) {
                while (index < 0) {
                    index += adapter!!.getItemsCount()
                }
                index %= adapter!!.getItemsCount()
            } else {
                return  // throw?
            }
        }

        //如果当前的索引不是传入的 索引
        if (index != currentItem) {

            /*
             * 如果需要动画, 就滚动到目标位置
             * 如果不需要动画, 重新设置布局
             */
            if (animated) {
                /*
                 * 开始滚动, 每个元素滚动间隔 400 ms, 滚动次数是 目标索引值 减去 当前索引值, 这是滚动的真实方法
                 */
                scroll(index - currentItem, SCROLLING_DURATION)
            } else {
                //所有布局设置为 null, 滚动位置设置为 0
                invalidateLayouts()
                val old = currentItem
                currentItem = index

                //便利回调元素改变监听器集合中的监听器元素中的元素改变方法
                notifyChangingListeners(old, currentItem)

                //重绘
                invalidate()
            }
        }
    }

    /**
     * 设置当前选中的条目, 没有动画, 当索引出错不做任何操作
     *
     * @param index 要设置的索引
     */
    fun setCurrentItem(index: Int) {
        setCurrentItem(index, false)
    }

    /**
     * 获取 WheelView 是否可以循环
     * -- 如果可循环 : 第一个之前是最后一个, 最后一个之后是第一个;
     * -- 如果不可循环 : 到第一个就不能上翻, 最后一个不能下翻
     *
     * @return
     */
    fun isCyclic(): Boolean {
        return isCyclic
    }

    /**
     * 设置 WheelView 循环标志
     *
     * @param isCyclic the flag to set
     */
    fun setCyclic(isCyclic: Boolean) {
        this.isCyclic = isCyclic
        invalidate()
        invalidateLayouts()
    }

    /**
     * 使布局无效
     * 将 选中条目 和 普通条目设置为 null, 滚动位置设置为0
     */
    private fun invalidateLayouts() {
        itemsLayout = null
        valueLayout = null
        scrollingOffset = 0
    }

    /**
     * 初始化资源
     */
    private fun initResourcesIfNecessary() {
        /*
         * 设置绘制普通条目的画笔, 允许抗拒齿, 允许 fake-bold
         * 设置文字大小为
         */
        if (itemsPaint == null) {
            itemsPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
            itemsPaint!!.textSize = TEXT_SIZE.toFloat()
        }

        /*
         * 设置绘制选中条目的画笔
         * 设置文字大小
         */if (valuePaint == null) {
            valuePaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
            valuePaint!!.textSize = SELECT_TEXT_SIZE.toFloat()
            valuePaint!!.setShadowLayer(0.1f, 0f, 0.1f, -0x3f3f40)
        }

//        //选中的条目背景
//        if (centerDrawable == null) {
//            centerDrawable = getContext().getResources().getDrawable(R.drawable.wheel_val);
//        }

        //创建顶部阴影图片
//        if (topShadow == null) {
//        	/*
//        	 * 构造方法中传入颜色渐变方向
//        	 * 阴影颜色
//        	 */
//            topShadow = new GradientDrawable(Orientation.TOP_BOTTOM, SHADOWS_COLORS);
//        }
//
//        //创建底部阴影图片
//        if (bottomShadow == null) {
//            bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP, SHADOWS_COLORS);
//        }
    }

    /**
     * 计算布局期望的高度
     *
     * @param layout 组件的布局的
     * @return 布局需要的高度
     */
    private fun getDesiredHeight(layout: Layout?): Int {
        if (layout == null) {
            return 0
        }

        /*
         * 布局需要的高度是 条目个数 * 可见条目数 减去 顶部和底部隐藏的一部份 减去 额外的条目高度
         */
        var desired = itemHeight * visibleItems - ITEM_OFFSET * 2 - ADDITIONAL_ITEM_HEIGHT

        // 将计算的布局高度 与 最小高度比较, 取最大值
        desired = Math.max(desired, suggestedMinimumHeight)
        return desired
    }

    /**
     * 根据条目获取字符串
     *
     * @param index 条目索引
     * @return 条目显示的字符串
     */
    private fun getTextItem(index: Int): String? {
        var index = index
        if (adapter == null || adapter!!.getItemsCount() == 0) {
            return null
        }
        //适配器显示的字符串个数
        val count = adapter!!.getItemsCount()

        //考虑 index 小于 0 的情况
        if ((index < 0 || index >= count) && !isCyclic) {
            return null
        } else {
            while (index < 0) {
                index = count + index
            }
        }

        //index 大于 0
        index %= count
        return adapter!!.getItem(index)
    }

    /**
     * 根据当前值创建 字符串
     *
     * @param useCurrentValue 是否在滚动
     * @return the text
     * 生成的字符串
     */
    private fun buildText(useCurrentValue: Boolean): String {
        //创建字符串容器
        val itemsText = StringBuilder()
        //计算出显示的条目相对位置, 例如显示 5个, 第 3 个是正中见选中的布局
        val addItems = visibleItems / 2 + 1

        /*
         * 遍历显示的条目
         * 获取当前显示条目 上下 各 addItems 个文本, 将该文本添加到显示文本中去
         * 如果不是最后一个 都加上回车
         */for (i in currentItem - addItems..currentItem + addItems) {
            //如果在滚动
            if (useCurrentValue || i != currentItem) {
                val text = getTextItem(i)
                if (text != null) {
                    itemsText.append(text)
                }
            }
            if (i < currentItem + addItems) {
                itemsText.append("\n")
            }
        }
        return itemsText.toString()
    }//如果获取的最大条目宽度不为 -1, 可以直接返回该条目宽度
    /*
* 遍历当前显示的条目, 获取字符串长度最长的那个, 返回这个最长的字符串长度
*/
    /**
     * 返回 条目的字符串
     *
     * @return 条目最大宽度
     */
    private val maxTextLength: Int
        private get() {
            val adapter = getAdapter() ?: return 0

            //如果获取的最大条目宽度不为 -1, 可以直接返回该条目宽度
            val adapterLength = adapter.getMaximumLength()
            if (adapterLength > 0) {
                return adapterLength
            }
            var maxText: String? = null
            val addItems = visibleItems / 2
            /*
      * 遍历当前显示的条目, 获取字符串长度最长的那个, 返回这个最长的字符串长度
      */for (i in Math.max(currentItem - addItems, 0) until Math.min(
                currentItem + visibleItems,
                adapter.getItemsCount()
            )) {
                val text = adapter.getItem(i)
                if (text != null && (maxText == null || maxText.length < text.length)) {
                    maxText = text
                }
            }
            return maxText?.length ?: 0
        }

    /**
     * 计算宽度并创建文字布局
     *
     * @param widthSize 输入的布局宽度
     * @param mode      布局模式
     * @return 计算的宽度
     */
    private fun calculateLayoutWidth(widthSize: Int, mode: Int): Int {
        initResourcesIfNecessary()
        var width = widthSize

        //获取最长的条目显示字符串字符个数
        val maxLength = maxTextLength
        itemsWidth = if (maxLength > 0) {
            /*
             * 使用方法 FloatMath.ceil() 方法有以下警告
             * Use java.lang.Math#ceil instead of android.util.FloatMath#ceil() since it is faster as of API 8
             */
            //float textWidth = FloatMath.ceil(Layout.getDesiredWidth("0", itemsPaint));
            //向上取整  计算一个字符串宽度
            val textWidth =
                Math.ceil(Layout.getDesiredWidth("0", itemsPaint).toDouble())
                    .toFloat()

            //获取字符串总的宽度
            (maxLength * textWidth).toInt()
        } else {
            0
        }

        //总宽度加上一些间距
        itemsWidth += ADDITIONAL_ITEMS_SPACE // make it some more

        //计算 label 的长度
        labelWidth = 0
        if (label != null && label!!.length > 0) {
            labelWidth = Math.ceil(Layout.getDesiredWidth(label, valuePaint).toDouble()).toInt()
            //labelWidth = (int) FloatMath.ceil(Layout.getDesiredWidth(label, valuePaint));
        }
        var recalculate = false
        //精准模式
        if (mode == MeasureSpec.EXACTLY) {
            //精准模式下, 宽度就是给定的宽度
            width = widthSize
            recalculate = true
        } else {
            //未定义模式
            width = itemsWidth + labelWidth + 2 * PADDING
            if (labelWidth > 0) {
                width += LABEL_OFFSET
            }

            // 获取 ( 计算出来的宽度 与 最小宽度的 ) 最大值
            width = Math.max(width, suggestedMinimumWidth)

            //最大模式 如果 给定的宽度 小于 计算出来的宽度, 那么使用最小的宽度 ( 给定宽度 | 计算出来的宽度 )
            if (mode == MeasureSpec.AT_MOST && widthSize < width) {
                width = widthSize
                recalculate = true
            }
        }

        /*
         * 重新计算宽度 , 如果宽度是给定的宽度, 不是我们计算出来的宽度, 需要重新进行计算
         * 重新计算的宽度是用于
         *
         * 计算 itemsWidth , 这个与返回的 宽度无关, 与创建布局有关
         */if (recalculate) {
            val pureWidth = width - LABEL_OFFSET - 2 * PADDING
            if (pureWidth <= 0) {
                labelWidth = 0
                itemsWidth = labelWidth
            }
            if (labelWidth > 0) {
                val newWidthItems = itemsWidth.toDouble() * pureWidth / (itemsWidth + labelWidth)
                itemsWidth = newWidthItems.toInt()
                labelWidth = pureWidth - itemsWidth
            } else {
                itemsWidth = pureWidth + LABEL_OFFSET // no label
            }
        }
        if (itemsWidth > 0) {
            //创建布局
            createLayouts(itemsWidth, labelWidth)
        }
        return width
    }

    /**
     * 创建布局
     *
     * @param widthItems 布局条目宽度
     * @param widthLabel label 宽度
     */
    private fun createLayouts(widthItems: Int, widthLabel: Int) {
        /*
         * 创建普通条目布局
         * 如果 普通条目布局 为 null 或者 普通条目布局的宽度 大于 传入的宽度, 这时需要重新创建布局
         * 如果 普通条目布局存在, 并且其宽度小于传入的宽度, 此时需要将
         */
        if (itemsLayout == null || itemsLayout!!.width > widthItems) {

            /*
             * android.text.StaticLayout.StaticLayout(
             * CharSequence source, TextPaint paint,
             * int width, Alignment align,
             * float spacingmult, float spacingadd, boolean includepad)
             * 传入参数介绍 :
             * CharSequence source : 需要分行显示的字符串
             * TextPaint paint : 绘制字符串的画笔
             * int width : 条目的宽度
             * Alignment align : Layout 的对齐方式, ALIGN_CENTER 居中对齐, ALIGN_NORMAL 左对齐, Alignment.ALIGN_OPPOSITE 右对齐
             * float spacingmult : 行间距, 1.5f 代表 1.5 倍字体高度
             * float spacingadd : 基础行距上增加多少 , 真实行间距 等于 spacingmult 和 spacingadd 的和
             * boolean includepad :
             */
            itemsLayout = StaticLayout(
                buildText(isScrollingPerformed),
                itemsPaint,
                widthItems,
                if (widthLabel > 0) Layout.Alignment.ALIGN_CENTER else Layout.Alignment.ALIGN_CENTER,
                1F,
                ADDITIONAL_ITEM_HEIGHT.toFloat(),
                false
            )
        } else {
            //调用 Layout 内置的方法 increaseWidthTo 将宽度提升到指定的宽度
            itemsLayout!!.increaseWidthTo(widthItems)
        }

        /*
         * 创建选中条目
         */if (!isScrollingPerformed && (valueLayout == null || valueLayout!!.width > widthItems)) {
            val text = if (getAdapter() != null) getAdapter()!!.getItem(currentItem) else null
            valueLayout = StaticLayout(
                text ?: "",
                valuePaint,
                widthItems,
                if (widthLabel > 0) Layout.Alignment.ALIGN_CENTER else Layout.Alignment.ALIGN_CENTER,
                1F,
                ADDITIONAL_ITEM_HEIGHT.toFloat(),
                false
            )
        } else if (isScrollingPerformed) {
            valueLayout = null
        } else {
            valueLayout!!.increaseWidthTo(widthItems)
        }

        /*
         * 创建标签条目
         */if (widthLabel > 0) {
            if (labelLayout == null || labelLayout!!.width > widthLabel) {
                labelLayout = StaticLayout(
                    label, valuePaint, widthLabel, Layout.Alignment.ALIGN_NORMAL, 1F,
                    ADDITIONAL_ITEM_HEIGHT.toFloat(), false
                )
            } else {
                labelLayout!!.increaseWidthTo(widthLabel)
            }
        }
    }

    /*
     * 测量组件大小
     * (non-Javadoc)
     * @see android.view.View#onMeasure(int, int)
     */
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //获取宽度 和 高度的模式 和 大小
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        //宽度就是 计算的布局的宽度
        val width = calculateLayoutWidth(widthSize, widthMode)
        var height: Int
        /*
         * 精准模式
         * 		精准模式下 高度就是精确的高度
         */if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize

            //未定义模式 和 最大模式
        } else {
            //未定义模式下 获取布局需要的高度
            height = getDesiredHeight(itemsLayout)

            //最大模式下 获取 布局高度 和 布局所需高度的最小值
            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(height, heightSize)
            }
        }

        //设置组件的宽和高
        setMeasuredDimension(width, height)
    }

    /*
     * 绘制组件
     * (non-Javadoc)
     * @see android.view.View#onDraw(android.graphics.Canvas)
     */
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        //如果条目布局为 null, 就创建该布局
        if (itemsLayout == null) {
            /*
             * 如果 条目宽度为0, 说明该宽度没有计算, 先计算, 计算完之后会创建布局
             * 如果 条目宽度 大于 0, 说明已经计算过宽度了, 直接创建布局
             */
            if (itemsWidth == 0) {
                calculateLayoutWidth(width, MeasureSpec.EXACTLY)
            } else {
                //创建普通条目布局, 选中条目布局, 标签条目布局
                createLayouts(itemsWidth, labelWidth)
            }
        }

        //如果条目宽度大于0
        if (itemsWidth > 0) {
            canvas.save()
            // 使用平移方法忽略 填充的空间 和 顶部底部隐藏的一部份条目
            canvas.translate(PADDING.toFloat(), -ITEM_OFFSET.toFloat())
            //绘制普通条目
            drawItems(canvas)
            //绘制选中条目
            drawValue(canvas)
            canvas.restore()
        }

        //在中心位置绘制
        drawCenterRect(canvas)
        //绘制阴影
        drawShadows(canvas)
    }

    /**
     * Draws shadows on top and bottom of control
     *
     * @param canvas the canvas for drawing
     */
    private fun drawShadows(canvas: Canvas) {
//        topShadow.setBounds(0, 0, getWidth(), getHeight() / visibleItems);
//        topShadow.draw(canvas);
//
//        bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems, getWidth(), getHeight());
//        bottomShadow.draw(canvas);
    }

    /**
     * 绘制选中条目
     *
     * @param canvas 画布
     */
    @SuppressLint("ClickableViewAccessibility")
    private fun drawValue(canvas: Canvas) {
        valuePaint!!.color = VALUE_TEXT_COLOR

        //将当前 View 状态属性值 转为整型集合, 赋值给 普通条目布局的绘制属性
        valuePaint!!.drawableState = drawableState
        val bounds = Rect()
        //获取选中条目布局的边界
        itemsLayout!!.getLineBounds(visibleItems / 2, bounds)

        // 绘制标签
        if (labelLayout != null) {
            canvas.save()
            canvas.translate((itemsLayout!!.width + LABEL_OFFSET).toFloat(), bounds.top.toFloat())
            labelLayout!!.draw(canvas)
            canvas.restore()
        }

        // 绘制选中条目
        if (valueLayout != null) {
            canvas.save()
            canvas.translate(0f, (bounds.top + scrollingOffset).toFloat())
            valueLayout!!.draw(canvas)
            canvas.restore()
        }
        setOnTouchListener { v, event ->
            when (event.action) {
                MotionEvent.ACTION_UP -> {
                    val itemHeight = height / visibleItems
                    if ((event.x >= 0
                                && event.x <= width)
                        && (event.y >= height / 2 - itemHeight / 2
                                && event.y <= height / 2 + itemHeight / 2)
                    ) {
                        if (listener != null && !isScrollingPerformed) {
                            listener!!.onClick()
                        }
                    }
                }
            }
            false
        }
    }

    var listener: OnCenterClickListener? = null

    interface OnCenterClickListener {
        fun onClick()
    }

    fun setOnCenterClickListener(listener: OnCenterClickListener?) {
        this.listener = listener
    }

    /**
     * 绘制普通条目
     *
     * @param canvas 画布
     */
    private fun drawItems(canvas: Canvas) {
        canvas.save()

        //获取 y 轴 定点高度
        val top = itemsLayout!!.getLineTop(1)
        canvas.translate(0f, (-top + scrollingOffset).toFloat())

        //设置画笔颜色
        itemsPaint!!.color = ITEMS_TEXT_COLOR
        //将当前 View 状态属性值 转为整型集合, 赋值给 普通条目布局的绘制属性
        itemsPaint!!.drawableState = drawableState
        //将布局绘制到画布上
        itemsLayout!!.draw(canvas)
        canvas.restore()
    }

    /**
     * 绘制当前选中条目的背景图片
     *
     * @param canvas 画布
     */
    private fun drawCenterRect(canvas: Canvas) {
//        int center = getHeight() / 2;
//        int offset = getItemHeight() / 2;
//        centerDrawable.setBounds(0, center - offset, getWidth(), center + offset);
//        centerDrawable.draw(canvas);
    }

    /*
     * 继承自 View 的触摸事件, 当出现触摸事件的时候, 就会回调该方法
     * (non-Javadoc)
     * @see android.view.View#onTouchEvent(android.view.MotionEvent)
     */
    override fun onTouchEvent(event: MotionEvent): Boolean {
        //获取适配器
        val adapter = getAdapter() ?: return true

        /*
         * gestureDetector.onTouchEvent(event) : 分析给定的动作, 如果可用, 调用 手势检测器的 onTouchEvent 方法
         * -- 参数解析 : ev , 触摸事件
         * -- 返回值 : 如果手势监听器成功执行了该方法, 返回true, 如果执行出现意外 返回 false;
         */if (!gestureDetector!!.onTouchEvent(event) && event.action == MotionEvent.ACTION_UP) {
            justify()
        }
        return true
    }

    /**
     * 滚动 WheelView
     *
     * @param delta 滚动的值
     */
    private fun doScroll(delta: Int) {
        scrollingOffset += delta

        //计算滚动的条目数, 使用滚动的值 处于 单个条目高度, 注意计算整数值
        var count = scrollingOffset / itemHeight
        /*
         * pos 是滚动后的目标元素索引
         * 计算当前位置, 当前条目数 减去 滚动的条目数
         * 注意 滚动条目数可正 可负
         */
        var pos = currentItem - count
        //如果是可循环的, 并且条目数大于0
        if (isCyclic && adapter!!.getItemsCount() > 0) {
            //设置循环, 如果位置小于0, 那么该位置就显示最后一个元素
            while (pos < 0) {
                pos += adapter!!.getItemsCount()
            }
            //如果位置正无限大, 模条目数 取余
            pos %= adapter!!.getItemsCount()

            // (前提 : 不可循环  条目数大于0, 可循环 条目数小于0, 条目数小于0, 不可循环) , 如果滚动在执行
        } else if (isScrollingPerformed) {
            //位置一旦小于0, 计算的位置就赋值为 0, 条目滚动数为0
            if (pos < 0) {
                count = currentItem
                pos = 0

                //位置大于条目数的时候, 当前位置等于(条目数 - 1), 条目滚动数等于 当前位置 减去 (条目数 - 1)
            } else if (pos >= adapter!!.getItemsCount()) {
                count = currentItem - adapter!!.getItemsCount() + 1
                pos = adapter!!.getItemsCount() - 1
            }
        } else {
            // fix position
            pos = Math.max(pos, 0)
            pos = Math.min(pos, adapter!!.getItemsCount() - 1)
        }

        //滚动的高度
        val offset = scrollingOffset

        /*
         * 如果当前位置不是滚动后的目标位置, 就将当前位置设置为目标位置
         * 否则就重绘组件
         */if (pos != currentItem) {
            setCurrentItem(pos, false)
        } else {
            //重绘组件
            invalidate()
        }

        // 将滚动后剩余的小数部分保存
        scrollingOffset = offset - count * itemHeight
        if (scrollingOffset > height) {
            scrollingOffset = scrollingOffset % height + height
        }
    }

    /**
     * 手势监听器
     */
    private val gestureListener: SimpleOnGestureListener = object : SimpleOnGestureListener() {
        //按下操作
        override fun onDown(e: MotionEvent): Boolean {
            //如果滚动在执行
            if (isScrollingPerformed) {
                //滚动强制停止, 按下的时候不能继续滚动
                scroller!!.forceFinished(true)
                //清理信息
                clearMessages()
                return true
            }
            return false
        }

        /*
         * 手势监听器监听到 滚动操作后回调
         *
         * 参数解析 :
         * MotionEvent e1 : 触发滚动时第一次按下的事件
         * MotionEvent e2 : 触发当前滚动的移动事件
         * float distanceX : 自从上一次调用 该方法 到这一次 x 轴滚动的距离,
         * 				注意不是 e1 到 e2 的距离, e1 到 e2 的距离是从开始滚动到现在的滚动距离
         * float distanceY : 自从上一次回调该方法到这一次 y 轴滚动的距离
         *
         * 返回值 : 如果事件成功触发, 执行完了方法中的操作, 返回true, 否则返回 false
         * (non-Javadoc)
         * @see android.view.GestureDetector.SimpleOnGestureListener#onScroll(android.view.MotionEvent, android.view.MotionEvent, float, float)
         */
        override fun onScroll(
            e1: MotionEvent,
            e2: MotionEvent,
            distanceX: Float,
            distanceY: Float
        ): Boolean {
            //开始滚动, 并回调滚动监听器集合中监听器的 开始滚动方法
            startScrolling()
            doScroll(-distanceY.toInt())
            return true
        }

        /*
         * 当一个急冲手势发生后 回调该方法, 会计算出该手势在 x 轴 y 轴的速率
         *
         * 参数解析 :
         * -- MotionEvent e1 : 急冲动作的第一次触摸事件;
         * -- MotionEvent e2 : 急冲动作的移动发生的时候的触摸事件;
         * -- float velocityX : x 轴的速率
         * -- float velocityY : y 轴的速率
         *
         * 返回值 : 如果执行完毕返回 true, 否则返回false, 这个就是自己定义的
         *
         * (non-Javadoc)
         * @see android.view.GestureDetector.SimpleOnGestureListener#onFling(android.view.MotionEvent, android.view.MotionEvent, float, float)
         */
        override fun onFling(
            e1: MotionEvent,
            e2: MotionEvent,
            velocityX: Float,
            velocityY: Float
        ): Boolean {
            //计算上一次的 y 轴位置, 当前的条目高度 加上 剩余的 不够一行高度的那部分
            lastScrollY = currentItem * itemHeight + scrollingOffset
            //如果可以循环最大值是无限大, 不能循环就是条目数的高度值
            val maxY = if (isCyclic) 0x7FFFFFFF else adapter!!.getItemsCount() * itemHeight
            val minY = if (isCyclic) -maxY else 0
            /*
             * Scroll 开始根据一个急冲手势滚动, 滚动的距离与初速度有关
             * 参数介绍 :
             * -- int startX : 开始时的 X轴位置
             * -- int startY : 开始时的 y轴位置
             * -- int velocityX : 急冲手势的 x 轴的初速度, 单位 px/s
             * -- int velocityY : 急冲手势的 y 轴的初速度, 单位 px/s
             * -- int minX : x 轴滚动的最小值
             * -- int maxX : x 轴滚动的最大值
             * -- int minY : y 轴滚动的最小值
             * -- int maxY : y 轴滚动的最大值
             */scroller!!.fling(0, lastScrollY, 0, -velocityY.toInt() / 2, 0, 0, minY, maxY)
            setNextMessage(MESSAGE_SCROLL)
            return true
        }
    }
    // Handler 中的  Message 信息
    /**
     * 滚动信息
     */
    private val MESSAGE_SCROLL = 0

    /**
     * 调整信息
     */
    private val MESSAGE_JUSTIFY = 1

    /**
     * 清空之前的 Handler 队列, 发送下一个消息到 Handler 中
     *
     * @param message 要发送的消息
     */
    private fun setNextMessage(message: Int) {
        //清空 Handler 队列中的  what 消息
        clearMessages()
        //发送消息到 Handler 中
        animationHandler.sendEmptyMessage(message)
    }

    /**
     * 清空队列中的信息
     */
    private fun clearMessages() {
        //删除 Handler 执行队列中的滚动操作
        animationHandler.removeMessages(MESSAGE_SCROLL)
        animationHandler.removeMessages(MESSAGE_JUSTIFY)
    }

    /**
     * 动画控制器
     * animation handler
     *
     *
     * 可能会造成内存泄露 : 添加注解 HandlerLeak
     * Handler 类应该应该为static类型，否则有可能造成泄露。
     * 在程序消息队列中排队的消息保持了对目标Handler类的应用。
     * 如果Handler是个内部类，那 么它也会保持它所在的外部类的引用。
     * 为了避免泄露这个外部类，应该将Handler声明为static嵌套类，并且使用对外部类的弱应用。
     */
    @SuppressLint("HandlerLeak")
    private val animationHandler: Handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            //回调该方法获取当前位置, 如果返回true, 说明动画还没有执行完毕
            scroller!!.computeScrollOffset()
            //获取当前 y 位置
            var currY = scroller!!.currY
            //获取已经滚动了的位置, 使用上一次位置 减去 当前位置
            val delta = lastScrollY - currY
            lastScrollY = currY
            if (delta != 0) {
                //改变值不为 0 , 继续滚动
                doScroll(delta)
            }

            /*
             * 如果滚动到了指定的位置, 滚动还没有停止
             * 这时需要强制停止
             */if (Math.abs(currY - scroller!!.finalY) < MIN_DELTA_FOR_SCROLLING) {
                currY = scroller!!.finalY
                scroller!!.forceFinished(true)
            }

            /*
             * 如果滚动没有停止
             * 再向 Handler 发送一个停止
             */if (!scroller!!.isFinished) {
                sendEmptyMessage(msg.what)
            } else if (msg.what == MESSAGE_SCROLL) {
                justify()
            } else {
                finishScrolling()
            }
        }
    }

    /**
     * 调整 WheelView
     */
    private fun justify() {
        if (adapter == null) {
            return
        }
        //上一次的 y 轴的位置为 0
        lastScrollY = 0
        var offset = scrollingOffset
        val itemHeight = itemHeight
        /*
         * 当滚动补偿 大于 0, 说明还有没有滚动的部分,  needToIncrease 是 当前条目是否小于条目数
         * 如果 滚动补偿不大于 0,  needToIncrease 是当前条目是否大于 0
         */
        val needToIncrease =
            if (offset > 0) currentItem < adapter!!.getItemsCount() else currentItem > 0
        if ((isCyclic || needToIncrease) && Math.abs(offset.toFloat()) > itemHeight.toFloat() / 2) {
            if (offset < 0) offset += itemHeight + MIN_DELTA_FOR_SCROLLING else offset -= itemHeight + MIN_DELTA_FOR_SCROLLING
        }
        if (Math.abs(offset) > MIN_DELTA_FOR_SCROLLING) {
            scroller!!.startScroll(0, 0, 0, offset, SCROLLING_DURATION)
            setNextMessage(MESSAGE_JUSTIFY)
        } else {
            finishScrolling()
        }
    }

    /**
     * WheelView 开始滚动
     */
    private fun startScrolling() {
        //如果没有滚动, 将滚动状态 isScrollingPerformed 设为 true
        if (!isScrollingPerformed) {
            isScrollingPerformed = true
            //通知监听器开始滚动 回调所有的 滚动监听集合中 的 开始滚动方法
            notifyScrollingListenersAboutStart()
        }
    }

    /**
     * 结束滚动
     * 设置滚动状态为 false, 回调滚动监听器的停止滚动方法
     */
    fun finishScrolling() {
        if (isScrollingPerformed) {
            notifyScrollingListenersAboutEnd()
            isScrollingPerformed = false
        }
        //设置布局无效
        invalidateLayouts()
        //重绘布局
        invalidate()
    }

    /**
     * 滚动 WheelView
     *
     *
     * // * @param itemsToSkip 滚动的元素个数
     *
     * @param time 每次滚动的间隔
     */
    fun scroll(itemsToScroll: Int, time: Int) {
        //如果有滚动强制停止
        scroller!!.forceFinished(true)
        lastScrollY = scrollingOffset
        val offset = itemsToScroll * itemHeight

        /*
         * 给定 一个开始点, 滚动距离, 滚动间隔, 开始滚动
         *
         * 参数解析 :
         * 1. 开始的 x 轴位置
         * 2. 开始的 y 轴位置
         * 3. 要滚动 x 轴距离
         * 4. 要滚动 y 轴距离
         * 5. 滚动花费的时间
         */scroller!!.startScroll(0, lastScrollY, 0, offset - lastScrollY, time)
        setNextMessage(MESSAGE_SCROLL)

        //设置开始滚动状态, 并回调滚动监听器方法
        startScrolling()
    }

    companion object {
        /**
         * 滚动花费时间 Scrolling duration
         */
        private const val SCROLLING_DURATION = 400

        /**
         * 最小的滚动值, 每次最少滚动一个单位
         */
        private const val MIN_DELTA_FOR_SCROLLING = 1

        /**
         * 当前条目中的文字颜色
         */
        private var VALUE_TEXT_COLOR = -0x1000000

        /**
         * 非当前条目的文字颜色
         */
        private var ITEMS_TEXT_COLOR = -0x333334

        /**
         * 顶部和底部的阴影颜色
         */
        //private static final int[] SHADOWS_COLORS = new int[] { 0xFF5436EE, 0x0012CEAE, 0x0012CEAE };
        private val SHADOWS_COLORS = intArrayOf(0x00000000, 0x00000000, 0x00000000)

        /**
         * 额外的条目高度
         */
        private var ADDITIONAL_ITEM_HEIGHT = 70

        /**
         * 字体大小
         */
        private var TEXT_SIZE = 30

        /**
         * 选中字体大小
         */
        private var SELECT_TEXT_SIZE = 40

        /**
         * 顶部 和 底部 条目的隐藏大小,
         * 如果是正数 会隐藏一部份,
         * 0 顶部 和 底部的字正好紧贴 边缘,
         * 负数时 顶部和底部 与 字有一定间距
         */
        private var ITEM_OFFSET = -TEXT_SIZE

        /**
         * Additional width for items layout
         */
        private const val ADDITIONAL_ITEMS_SPACE = 10

        /**
         * Label offset
         */
        private const val LABEL_OFFSET = 8

        /**
         * 左右间距
         */
        private const val PADDING = 10

        /**
         * 默认的可显示的条目数
         */
        private const val DEF_VISIBLE_ITEMS = 5
    }
    /**
     * 构造方法
     */
    /**
     * 构造方法
     */
    /**
     * 构造方法
     */
    init {
        initData(context, attrs)
    }
}